package cn.lili.modules.connect.serviceimpl;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.lili.cache.Cache;
import cn.lili.cache.CachePrefix;
import cn.lili.common.context.ThreadContextHolder;
import cn.lili.common.enums.ClientTypeEnum;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.security.token.Token;
import cn.lili.common.utils.BeanUtil;
import cn.lili.common.utils.CookieUtil;
import cn.lili.common.utils.HttpUtils;
import cn.lili.common.utils.StringUtils;
import cn.lili.modules.connect.entity.Connect;
import cn.lili.modules.connect.entity.dto.ConnectAuthUser;
import cn.lili.modules.connect.entity.dto.WechatMPLoginParams;
import cn.lili.modules.connect.entity.enums.ConnectEnum;
import cn.lili.modules.connect.mapper.ConnectMapper;
import cn.lili.modules.connect.service.ConnectService;
import cn.lili.modules.member.entity.dos.Extension;
import cn.lili.modules.member.entity.dos.GradeLevel;
import cn.lili.modules.member.entity.dos.Member;
import cn.lili.modules.member.entity.dos.Partner;
import cn.lili.modules.member.entity.dto.ConnectQueryDTO;
import cn.lili.modules.member.entity.vo.GradeVO;
import cn.lili.modules.member.entity.vo.MemberVO;
import cn.lili.modules.member.mapper.GradeLevelMapper;
import cn.lili.modules.member.mapper.GradeMapper;
import cn.lili.modules.member.mapper.PartnerMapper;
import cn.lili.modules.member.service.EextensionService;
import cn.lili.modules.member.service.MemberService;
import cn.lili.modules.member.token.MemberTokenGenerate;
import cn.lili.modules.payment.entity.enums.CashierEnum;
import cn.lili.modules.permission.entity.dos.Role;
import cn.lili.modules.permission.mapper.RoleMapper;
import cn.lili.modules.system.entity.dos.Region;
import cn.lili.modules.system.entity.dos.Setting;
import cn.lili.modules.system.entity.dto.connect.WechatConnectSetting;
import cn.lili.modules.system.entity.dto.connect.dto.WechatConnectSettingItem;
import cn.lili.modules.system.entity.enums.SettingEnum;
import cn.lili.modules.system.mapper.RegionMapper;
import cn.lili.modules.system.service.SettingService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.naming.NoPermissionException;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.Security;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 联合登陆接口实现
 *
 * @author Chopper
 */
@Slf4j
@Service
public class ConnectServiceImpl extends ServiceImpl<ConnectMapper, Connect> implements ConnectService {

	@Autowired
	private SettingService settingService;
	@Autowired
	private MemberService memberService;
	@Autowired
	private MemberTokenGenerate memberTokenGenerate;
	@Autowired
	private Cache cache;

	@Autowired
	private EextensionService eextensionService;

	@Autowired
	private RoleMapper roleMapper;

	@Autowired
	private PartnerMapper partnerMapper;

	@Autowired
	private GradeLevelMapper gradeLevelMapper;
	@Autowired
	private GradeMapper gradeMapper;
	@Autowired
	private RegionMapper regionMapper;

	static boolean AUTO_REGION = true;

	@Override
	public Token unionLoginCallback(String type, String unionid, String uuid, boolean longTerm)
			throws NoPermissionException {

		try {
			LambdaQueryWrapper<Connect> queryWrapper = new LambdaQueryWrapper<>();
			queryWrapper.eq(Connect::getUnionId, unionid);
			queryWrapper.eq(Connect::getUnionType, type);
			// 查询绑定关系
			Connect connect = this.getOne(queryWrapper);
			if (connect == null) {
				throw new NoPermissionException("未绑定用户");
			}
			// 查询会员
			Member member = memberService.getById(connect.getUserId());
			// 如果未绑定会员，则把刚才查询到的联合登录表数据删除
			if (member == null) {
				this.remove(queryWrapper);
				throw new NoPermissionException("未绑定用户");
			}
			return memberTokenGenerate.createToken(member.getUsername(), longTerm);
		} catch (NoPermissionException e) {
			throw e;
		}
	}

	@Override
	public Token unionLoginCallback(String type, ConnectAuthUser authUser, String uuid) {

		Token token;
		try {
			token = this.unionLoginCallback(type, authUser.getUuid(), uuid, false);
		} catch (NoPermissionException e) {
			if (AUTO_REGION) {
				token = memberService.autoRegister(authUser);
				return token;
			} else {
				// 写入cookie
				CookieUtil.addCookie(CONNECT_COOKIE, uuid, 1800, ThreadContextHolder.getHttpResponse());
				CookieUtil.addCookie(CONNECT_TYPE, type, 1800, ThreadContextHolder.getHttpResponse());
				// 自动登录失败，则把信息缓存起来
				cache.put(ConnectService.cacheKey(type, uuid), authUser, 30L, TimeUnit.MINUTES);
				throw new ServiceException(ResultCode.USER_NOT_BINDING);
			}
		} catch (Exception e) {
			log.error("联合登陆异常：", e);
			throw new ServiceException(ResultCode.ERROR);
		}
		return token;
	}

	@Override
	public void bind(String unionId, String type) {
		AuthUser authUser = UserContext.getCurrentUser();
		Connect connect = new Connect(authUser.getId(), unionId, type);
		this.save(connect);
	}

	@Override
	public void unbind(String type) {

		LambdaQueryWrapper<Connect> queryWrapper = new LambdaQueryWrapper<>();

		queryWrapper.eq(Connect::getUserId, UserContext.getCurrentUser().getId());
		queryWrapper.eq(Connect::getUnionType, type);

		this.remove(queryWrapper);
	}

	@Override
	public List<String> bindList() {
		LambdaQueryWrapper<Connect> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.eq(Connect::getUserId, UserContext.getCurrentUser().getId());
		List<Connect> connects = this.list(queryWrapper);
		List<String> keys = new ArrayList<>();
		connects.forEach(item -> {
			keys.add(item.getUnionType());
		});
		return keys;
	}

	@Override
	public Token appLoginCallback(ConnectAuthUser authUser, String uuid) {
		try {
			return this.unionLoginCallback(authUser.getSource(), authUser.getUuid(), uuid, true);
		} catch (NoPermissionException e) {
			return memberService.autoRegister(authUser);
		}
	}

	@Override
	public Map<String,Object> miniProgramAutoLogin(WechatMPLoginParams params) {

		Object cacheData = cache.get(CachePrefix.WECHAT_SESSION_PARAMS.getPrefix() + params.getUuid());
		Map<String, String> map = new HashMap<>(3);
		if (cacheData == null) {
			// 得到微信小程序联合登陆信息
			JSONObject json = this.getConnect(params.getCode(), params.getOwner());
			// 存储session key 后续登录用得到
			String sessionKey = json.getStr("session_key");
			String unionId = json.getStr("unionid");
			String openId = json.getStr("openid");
			map.put("sessionKey", sessionKey);
			map.put("unionId", unionId);
			map.put("openId", openId);
			cache.put(CachePrefix.WECHAT_SESSION_PARAMS.getPrefix() + params.getUuid(), map, 7 * 24 * 60L,
					TimeUnit.MINUTES);
		} else {
			map = (Map<String, String>) cacheData;
		}
		// 微信联合登陆参数

		return phoneMpBindAndLogin(map.get("sessionKey"), params, map.get("openId"), map.get("unionId"),
				params.getOwner());
	}

	/**
	 * 通过微信返回等code 获取openid 等信息
	 *
	 * @param code
	 * @return
	 */
	public JSONObject getConnect(String code, String owner) {
		WechatConnectSettingItem setting = getWechatMPSetting(owner);
		// wx2ba9c58bb4ef51f7 2095728d9a8c421bcf588a3a95560ba2
		// wxee065f35890ce4c3 de5c7d02eb72e93b7cc9354d0be1b58e
		String url = "https://api.weixin.qq.com/sns/jscode2session?" + "appid=" + setting.getAppId() + "&" + "secret="
				+ setting.getAppSecret() + "&" + "js_code=" + code + "&" + "grant_type=authorization_code";
		String content = HttpUtils.doGet(url, "UTF-8", 100000, 1000000);
		log.error(content);
		return JSONUtil.parseObj(content);
	}

	/**
	 * 手机号 绑定 且 自动登录
	 *
	 * @param sessionKey
	 *            微信sessionKey
	 * @param params
	 *            微信小程序自动登录参数
	 * @param openId
	 *            微信openid
	 * @param unionId
	 *            微信unionid
	 * @return
	 */
	public Map<String,Object> phoneMpBindAndLogin(String sessionKey, WechatMPLoginParams params, String openId, String unionId,
			String owner) {
		Map<String,Object> map = new HashMap<>();
		String encryptedData = params.getEncryptedData(), iv = params.getIv();
		JSONObject userInfo = this.getUserInfo(encryptedData, sessionKey, iv);
		log.info("联合登陆返回：{}", userInfo.toString());
		String phone = (String) userInfo.get("purePhoneNumber");
//		 String phone ="19932149193";
		// 手机号登录
		QueryWrapper<Member> queryWrapper = new QueryWrapper<>();
		queryWrapper.eq("m.mobile", phone);
		// queryWrapper.and(wrapper -> wrapper.isNull("g.owner").or().like("g.owner",
		// "BUYER"));
		queryWrapper.orderByAsc("c.LEVEL");
		queryWrapper.last("limit 1");
		String location = "";
		// 定位
		if (StringUtils.isNotEmpty(params.getAdCode()) && !"undefined".equals(params.getAdCode())
				&& !"".equals(params.getAdCode()) && !"0".equals(params.getAdCode())) {
			location = params.getAdCode();
		}
		MemberVO member = memberService.findByPhone(queryWrapper);
		// 如果不存在会员，则进行绑定微信openid 和 unionid，并且登录
		if (member != null) {
			Member newMember = new Member();
			member.setNickName(params.getNickName());
			if (StringUtils.isEmpty(member.getLocation()) && !"".equals(location)) {
				member.setLocation(location);
			}
			BeanUtil.copyProperties(member, newMember);
			memberService.updateById(newMember);
			bindMpMember(openId, unionId, member, owner);
			map.put("firstLogin",false);
			map.put("token",memberTokenGenerate.createToken(member.getUsername(), true));
			return map;
		} else {
			member = new MemberVO();
			map.put("firstLogin",true);
		}

		// 如果没有会员，则根据手机号注册会员
		Member newMember = new Member("m" + phone, "$2a$10$Tb44xSPTCuuWxxZd5ebmfO0pq/J9XSHSqoKdi7ctFoKPHrcBShMLK",
				phone, params.getNickName(), params.getImage());
		if (!"".equals(location)) {
			newMember.setLocation(location);
		}
		newMember.setExtensionFlag(Boolean.TRUE);
		// newMember.setNoSecret(0);// 默认不免密
		memberService.save(newMember);
		newMember = memberService.findByUsername(newMember.getUsername());
		// 绑定合伙人信息
		// updatePartner(newMember.getId());
		// 绑定会员信息
		updateMember(newMember.getId(), owner);
		member.setId(newMember.getId());
		bindMpMember(openId, unionId, member, owner);
		map.put("token",memberTokenGenerate.createToken(newMember.getUsername(), true));
		return map;
	}

	// 绑定合伙人信息
	void updatePartner(String memberId) {
		Partner partner = new Partner();
		// 获取时间加一年或加一月或加一天
		Date date = new Date();
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);// 设置起时间
		cal.add(Calendar.YEAR, 1);// 增加一年
		partner.setBeginTime(date);
		partner.setEndTime(cal.getTime());
		partner.setMemberId(memberId);
		partner.setPartnerState(0);
		QueryWrapper<Role> queryWrapper = new QueryWrapper<>();
		partner.setPartnerType(3);
		queryWrapper.eq("name", "推广员");
		queryWrapper.eq("parent_id", 0);
		Role role = roleMapper.selectOne(queryWrapper);
		partner.setRoleId(role.getId());
		partnerMapper.insert(partner);
		// 添加推广码
		Member member1 = new Member();
		member1.setId(memberId);
		member1.setPromotionCode(eextensionService.getExtension());
		memberService.updateById(member1);
	}

	// 绑定会员信息
	void updateMember(String memberId, String owner) {
		GradeLevel gradeLevel = new GradeLevel();
		// 获取时间加一年或加一月或加一天
		Date date = new Date();
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);// 设置起时间
		cal.add(Calendar.YEAR, 1);// 增加一年
		gradeLevel.setBeginTime(date);
		gradeLevel.setEndTime(cal.getTime());
		gradeLevel.setMemberId(memberId);
		gradeLevel.setGradeState(0);
		List<GradeVO> gradeVOS = gradeMapper.selectList(new QueryWrapper<>());
		if (CollectionUtils.isNotEmpty(gradeVOS)) {
			for (GradeVO grade : gradeVOS) {
				if (0 == grade.getMoney()) {
					gradeLevel.setGradeId(String.valueOf(grade.getId()));
				}
			}
		}
		gradeLevel.setOwner(owner);
		gradeLevelMapper.insert(gradeLevel);
	}

	@Override
	public Connect queryConnect(ConnectQueryDTO connectQueryDTO) {

		LambdaQueryWrapper<Connect> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper
				.eq(StringUtils.isNotEmpty(connectQueryDTO.getUserId()), Connect::getUserId,
						connectQueryDTO.getUserId())
				.eq(StringUtils.isNotEmpty(connectQueryDTO.getUnionType()), Connect::getUnionType,
						connectQueryDTO.getUnionType())
				.eq(StringUtils.isNotEmpty(connectQueryDTO.getUnionId()), Connect::getUnionId,
						connectQueryDTO.getUnionId());
		return this.getOne(queryWrapper);
	}

	// 通过用户查询openid
	@Override
	public List<Connect> findByMember(Member member){
		QueryWrapper<Member> queryWrapper = new QueryWrapper<>();
		queryWrapper
			.eq(StringUtils.isNotEmpty(member.getId()), "c.user_id", member.getId())
			.eq(StringUtils.isNotEmpty(member.getMobile()), "m.mobile", member.getMobile());
		return this.baseMapper.findByMember(queryWrapper);
	}

	/**
	 * 会员绑定 绑定微信小程序
	 * <p>
	 * 如果openid 已经绑定其他账号，则这里不作处理，如果未绑定，则绑定最新的会员 这样，微信小程序注册之后，其他app
	 * 公众号页面，都可以实现绑定自动登录功能
	 * </p>
	 *
	 * @param openId
	 * @param unionId
	 * @param member
	 */
	private void bindMpMember(String openId, String unionId, MemberVO member, String owner) {

		// 如果unionid 不为空 则为账号绑定unionid
		if (StringUtils.isNotEmpty(unionId)) {
			LambdaQueryWrapper<Connect> lambdaQueryWrapper = new LambdaQueryWrapper();
			lambdaQueryWrapper.eq(Connect::getUnionId, unionId);
			lambdaQueryWrapper.eq(Connect::getUserId, member.getId());
			lambdaQueryWrapper.eq(Connect::getUnionType, ConnectEnum.WECHAT.name());
			List<Connect> connects = this.list(lambdaQueryWrapper);
			if (connects.size() == 0) {
				Connect connect = new Connect();
				connect.setUnionId(unionId);
				connect.setUserId(member.getId());
				connect.setUnionType(ConnectEnum.WECHAT.name());
				this.save(connect);
			}
		} // 如果openid 不为空 则为账号绑定openid
		if (StringUtils.isNotEmpty(openId)) {
			LambdaQueryWrapper<Connect> lambdaQueryWrapper = new LambdaQueryWrapper();
			lambdaQueryWrapper.eq(Connect::getUnionId, openId);
			lambdaQueryWrapper.eq(Connect::getUserId, member.getId());
			lambdaQueryWrapper.eq(Connect::getUnionType, ConnectEnum.WECHAT_MP_OPEN_ID.name());
			List<Connect> connects = this.list(lambdaQueryWrapper);
			if (connects.size() == 0) {
				Connect connect = new Connect();
				connect.setUnionId(openId);
				connect.setUserId(member.getId());
				if ("STORE".equals(owner)) {
					// 商家
					connect.setUnionType(ConnectEnum.WECHAT_OPEN_ID.name());
				} else {
					// 用户
					connect.setUnionType(ConnectEnum.WECHAT_MP_OPEN_ID.name());
				}
				this.save(connect);
			}
		}

	}

	/**
	 * 获取微信小程序配置
	 *
	 * @return
	 */
	private WechatConnectSettingItem getWechatMPSetting(String owner) {
		Setting setting = settingService.get(SettingEnum.WECHAT_CONNECT.name());

		WechatConnectSetting wechatConnectSetting = JSONUtil.toBean(setting.getSettingValue(),
				WechatConnectSetting.class);

		if (wechatConnectSetting == null) {
			throw new ServiceException(ResultCode.WECHAT_CONNECT_NOT_EXIST);
		}
		// 寻找对应对微信小程序登录配置
		for (WechatConnectSettingItem wechatConnectSettingItem : wechatConnectSetting.getWechatConnectSettingItems()) {
			if ("STORE".equals(owner)) {
				if (wechatConnectSettingItem.getClientType().equals(ClientTypeEnum.WECHAT_MP2.name())) {
					return wechatConnectSettingItem;
				}
			} else {
				if (wechatConnectSettingItem.getClientType().equals(ClientTypeEnum.WECHAT_MP.name())) {
					return wechatConnectSettingItem;
				}
			}
		}

		throw new ServiceException(ResultCode.WECHAT_CONNECT_NOT_EXIST);
	}

	/**
	 * 解密，获取微信信息
	 *
	 * @param encryptedData
	 *            加密信息
	 * @param sessionKey
	 *            微信sessionKey
	 * @param iv
	 *            微信揭秘参数
	 * @return 用户信息
	 */
	public JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) {

		log.info("encryptedData:{},sessionKey:{},iv:{}", encryptedData, sessionKey, iv);
		// 被加密的数据
		byte[] dataByte = Base64.getDecoder().decode(encryptedData);
		// 加密秘钥
		byte[] keyByte = Base64.getDecoder().decode(sessionKey);
		// 偏移量
		byte[] ivByte = Base64.getDecoder().decode(iv);
		try {
			// 如果密钥不足16位，那么就补足. 这个if 中的内容很重要
			int base = 16;
			if (keyByte.length % base != 0) {
				int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
				byte[] temp = new byte[groups * base];
				Arrays.fill(temp, (byte) 0);
				System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
				keyByte = temp;
			}
			// 初始化
			Security.addProvider(new BouncyCastleProvider());
			Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
			SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
			AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
			parameters.init(new IvParameterSpec(ivByte));
			// 初始化
			cipher.init(Cipher.DECRYPT_MODE, spec, parameters);
			byte[] resultByte = cipher.doFinal(dataByte);
			if (null != resultByte && resultByte.length > 0) {
				String result = new String(resultByte, StandardCharsets.UTF_8);
				return JSONUtil.parseObj(result);
			}
		} catch (Exception e) {
			log.error("解密，获取微信信息错误", e);
		}
		throw new ServiceException(ResultCode.USER_CONNECT_ERROR);
	}
}